<?php
defined('BASEPATH') OR exit('No direct script access allowed');
/**
 * 差价合约操作类
 */
class CfdClass{
	protected $ci;
	private $redis;

	private $cfdConfig;
	private $productConfig;
	private $productData;
	private $denomConfig;
	private $name;
	private $dtype;
	private $db;
	private $log_db;

	function __construct(){
		$this->ci = & get_instance();

		//链接redis
		$this->ci->load->driver('cache');
		$this->ci->cache->redis->is_supported();
		$this->redis = $this->ci->cache->redis;

		$this->db = $this->ci->load->database('default',true);
		$this->log_db = $this->ci->load->database('log',true);
	}

	//设置产品
	public function setProduct($name, $platform='lmax'){
		// echo $name;
		$this->name = $name;
		$where = array('name'=>$name,'platform'=>$platform);
		$query = $this->db->select()->from("product")->where($where)->limit(1)->get();
		if ($query->row()) {
			$this->productData = $query->row_array();
		}else{
			return false;
		}
		$this->cfdConfig = $this->ci->config->item('cfdConfig');
		$this->productConfig = isset($this->cfdConfig[$name])?$this->cfdConfig[$name]:array();
		if(empty($this->productConfig)) return false;
		return true;
	}

	public function getCfdConfig(){
		return $this->cfdConfig;
	}

	public function getProductData(){
		return $this->productData;
	}

	public function getProductConfig(){
		return $this->productConfig;
	}

	public function setDb($db){
		$this->db = $db;
	}

	public function setLogDb($log_db){
		$this->log_db = $log_db;
	}

	//设置计价方式
	public function setDenom($dtype){
		$this->dtype = $dtype;
		$this->denomConfig = isset($this->productConfig[$dtype])?$this->productConfig[$dtype]:array();
		if(empty($this->denomConfig)) return false;
		return true;
	}

	public function getDenomConfig(){
		return $this->denomConfig;
	}

	public function getRedis(){
		return $this->redis->getClient($this->cfdConfig['common']['redisDB']);
	}

	/**
	 * 加
	 * @param array  $data_arr [description]
	 * @param int  $dot 保留位数
	 * @param boolean $addMax  是否变更max_transfer
	 * @param string  $write_conn [description]
	 */
	public function add($data_arr, $dot=2) {
		foreach ($data_arr as $key => $val) {
			$$key = $val;
		}
		unset($data_arr);
		$rs = false;
		$datetime = date('Y-m-d H:i:s');
		if(!isset($table) || $table=='') $table = 'user_info';
		if(!isset($ip) || $ip=='') $ip = '127.0.0.1';
		if(!isset($where) || $where=='') $where = array('uid'=>$uid);
		if($use_value<=0){
			return false;
		}
		try {
			$this->db->set($object, $object.'+'.$use_value, FALSE);
			// $this->db->set($object.'_block', $object.'_block-'.$use_value, FALSE);
			$this->db->where($where);
			$this->db->update($table);
			// 查询现有值
			$query = $this->db->select()->from($table)->where($where)->limit(1)->get();
			if ($query->row()) {
				$rs = $query->row_array();
			}else throw new Exception('L:'.__LINE__." $table query error!", 1);

			$log_arr = array(
				'action' => 'add',
				'name' => $name,
				'uid' => $uid,
				'old' => bcsub($rs[$object],$use_value,$dot),
				'new' => $rs[$object],
				'use_value' => $use_value,
				'comefrom' => isset($comefrom)?$comefrom:1,
				'source' =>	$source,
				'ip' => $ip,
				'add_time' => $add_time,
				'create_time' => $datetime,
				'status' => 1,
			);
			$logTb = splitTable('log_'.$object, $uid);
			if (!$this->log_db->insert($logTb, $log_arr)) throw new Exception('L:'.__LINE__." $logTb add insert error!", 1);
			$rs = true;
		} catch (Exception $e) {
			writeDebug('['.$datetime.'] 加'.$object.'失败! Err: '.$e->getMessage(), 'Doing');
		}
		return $rs;
	}


	/**
	 * 扣
	 * @param  array  $data_arr  [description]
	 * @param  boolean $reduceMax 是否变更max_transfer
	 * @param  string  $write_conn [description]
	 * @return [type]              [description]
	 */
	public function reduce($data_arr, $dot=2) {
		foreach ($data_arr as $key => $val) {
			$$key = $val;
		}
		unset($data_arr);
		$rs = false;
		$datetime = date('Y-m-d H:i:s');
		if(!isset($table) || $table=='') $table = 'user_info';
		if(!isset($ip) || $ip=='') $ip = '127.0.0.1';
		if(!isset($where) || $where=='') $where = array('uid'=>$uid,$object.'>='=>$use_value);
		if($use_value<=0){
			return false;
		}
		try {
			$this->db->set($object, $object.'-'.$use_value, FALSE);
			// $this->db->set($object.'_block', $object.'_block+'.$use_value, FALSE);
			$this->db->where($where);

			if(!$this->db->update($table)){
				throw new Exception('L:'.__LINE__." $object reduce update error!", 1);
			}
			// 查询现有值
			$where = array('uid'=>$uid);
			$query = $this->db->select()->from($table)->where($where)->limit(1)->get();
			if ($query->row()) {
				$rs = $query->row_array();
			}else throw new Exception('L:'.__LINE__." $table query error!", 1);

			$log_arr = array(
				'action' => 'reduce',
				'name' => $name,
				'uid' => $uid,
				'old' => bcadd($rs[$object],$use_value,$dot),
				'new' => $rs[$object],
				'use_value' => $use_value,
				'comefrom' => isset($comefrom)?$comefrom:1,
				'source' => $source,
				'ip' => $ip,
				'add_time' => $add_time,
				'create_time' => $datetime,
				'status' => 1,
			);
			$logTb = splitTable('log_'.$object, $uid);
			if (!$this->log_db->insert($logTb, $log_arr)) throw new Exception('L:'.__LINE__." $table reduce insert error!", 1);
			$rs = true;
		} catch (Exception $e) {
			writeDebug('['.$datetime.'] 扣'.$object.'失败! Err: '.$e->getMessage(), 'Doing');
		}

		return $rs;
	}

	public function open($data){

		// 初始化数据
		foreach($data as $key => $val) {
			$$key = $val;
		}
		unset($data);

		$redisData = $this->redisAllData();
		// 检查类型
		switch ($type) {
			case 0: // 市价单
				$price = 0.00;
				if ($action == 'ask') {//卖空
					$com = (isset($redisData['trade']['WCP_ASK_COM']) && $redisData['trade']['WCP_ASK_COM']>0)?$redisData['trade']['WCP_ASK_COM']:$this->denomConfig['default_com'];
				} else {//买多
					$com = (isset($redisData['trade']['WCP_BID_COM']) && $redisData['trade']['WCP_BID_COM']>0)?$redisData['trade']['WCP_BID_COM']:$this->denomConfig['default_com'];
				}
				break;
			case 1: // 限价单
				if(!array_key_exists($com, $this->denomConfig['floating_rang'][$type])) return jsonReturn(-1,"佣金限制错误",array(),TRUE); // 佣金错误
				if (empty($price) || !is_numeric($price)) return jsonReturn(-1,"无效操作[20000]",array(),TRUE); // 无效的操作
				// 只能是正数
				$price = abs($price);
				break;
			default:
				return jsonReturn(-1,"无效操作[20001]",array(),TRUE); // 无效的操作
		}

		// if($this->dtype=='ticket'){//体验券
			$com = 0;//没手续费点数
		// }

		// 判断价格
		$check = false;
		if ($action == 'ask') {//卖空
			$check  = (bccomp(0, $redisData['trade']['BID_PRICE'], 6) != -1);
		} else {//买多
			$check  = (bccomp(0, $redisData['trade']['ASK_PRICE'], 6) != -1);
		}
		if($check){
			return jsonReturn(-1,"不在交易时段",array(),TRUE); // 无效的操作
		}

		$settlementArr = explode(',',$this->productData['settlement']);
		$dst = $this->checkDST($this->name);
		$settlementStr = $settlementArr[$dst];

		//每个品种仓位最多500手
		$tradeLogTB = 'cfd_cfd_trade_log';
		$curTime = time();
		$timeA = strtotime(date('Y-m-d '.$settlementStr,strtotime('-1 day')));
		$timeB = strtotime(date('Y-m-d '.$settlementStr));
		$timeC = strtotime(date('Y-m-d '.$settlementStr,strtotime('+1 day')));
		if($curTime>=$timeA && $curTime<$timeB){
			$start_time = date('Y-m-d H:i:s',$timeA);
			$end_time = date('Y-m-d H:i:s',$timeB);
		}else if($curTime>=$timeB && $curTime<$timeC){
			$start_time = date('Y-m-d H:i:s',$timeB);
			$end_time = date('Y-m-d H:i:s',$timeC);
		}
		$this->db->where(array('uid'=>$uid,'name'=>$this->name,'date>='=>$start_time,'date<'=>$end_time));
		$this->db->where_in('source',array('10001','50001'));
		$this->db->select_sum('num');
		$query = $this->db->get($tradeLogTB);
		$sumArr = $query->row_array();
		if($sumArr['num']>=500){
			return jsonReturn(-1,"今日开仓数量已达上线",array(),TRUE);
		}
		//每人最多只能3个持仓
		$positionTB = 'cfd_cfd_position';
		$this->db->where(array('name'=>$this->name,'uid'=>$uid,'flag'=>0));
		$posCount = $this->db->count_all_results($positionTB);
		if($posCount>=10){
			return jsonReturn(-1,"持仓单数已达上线",array(),TRUE);
		}
		//检查委托单量
		//代码待实现
		if($this->dtype=='ticket'){//券一天只能用一张

		}

		if ($num <=0 || !is_numeric($num) || strpos($num,".")!==false ) return jsonReturn(-1,"开仓数量错误",array(),TRUE);

		$datetime = date('Y-m-d H:i:s');
		$use_value = $this->countBZJ($num,$times,0,'open');//计算保证金
		if (!$use_value)  return jsonReturn(-1,"下单失败[30000]",array(),TRUE);
		$redis = $this->getRedis();
		try {
			$this->db->trans_start();//开始事务
			$this->log_db->trans_start();//开始事务
			// 开始事务
			$redis->multi();

			//扣款处理
			$reduceArr = array(
				'uid' => $uid,
				'name' => $this->name,
				'object' => $this->dtype,
				'use_value' => $use_value,
				'source' => $this->cfdConfig['doing'][$action]['open']['source'],
				'add_time' => $datetime,
			);
			if(!$this->reduce($reduceArr)){
				throw new Exception('余额不足，请重新填写数量', 1);
			}
			// 生成订单
			$data_arr = array(
				'uid'            => $uid,
				'name'			 => $this->name,
				'dtype'			 => $this->dtype,
				'type'           => $type,
				'times'			 => $times,
				'kp'             => 'open',
				'price'          => $price,
				'num'            => $num,
				'commission'     => $com,
				'cjnum'          => 0,
				'flag'           => 0, // 0=未完成, 1=已完成
				'status'         => 0, // 0=未处理, 1=处理中, 2=已处理
				'ip'             => get_ip(),
				'comefrom'       => isset($comefrom)?$comefrom:1,
				'create_time'    => $datetime,
				'lastest_modify' => $datetime
			);
			if(!$this->db->insert('cfd_cfd_'.$action,$data_arr)) throw new Exception('下单失败[30002]', 1);
			$orderno = $this->db->insert_id();
			//放入队列处理
			if(isset($orderno) && $orderno>0){
				$queue1Key = $this->cfdConfig['common']['queue1']."_".$this->name;
				$try  = 2; // 推两次都不行就不推了……
				do {
					$len = $redis->lPush($queue1Key, $action.'|'.$this->dtype.'|'.$orderno);
					if ($len != false) break;
					--$try;
				} while($try > 0);
				if ($len == false) throw new Exception('L:'.__LINE__.' 队列推送失败!', 1);
			}
			// DB 提交修改
			$this->db->trans_complete();
			$this->log_db->trans_complete();
			// Redis 提交修改
			$redis->exec();
			$this->flushUserCache($uid);
		} catch (Exception $e) {
			$this->db->trans_rollback();
			$this->log_db->trans_rollback();
			$redis->discard();
			return jsonReturn(-1,$e->getMessage(),array(),TRUE);
		}
		return jsonReturn(0,"success",array('orderno'=>$orderno),TRUE);
	}

	/**
	 * 平仓
	 * @param  array $data [description]
	 * @return mixed
	 */
	public function close($data_arr) {
		// 初始化数据
		foreach ($data_arr as $key => $val) {
			$$key = $val;
		}
		unset($data_arr);

		$redisData = $this->redisAllData();
		// 判断价格
		$check = false;
		if ($action == 'bid_ping') {//多单平仓
			$check  = (bccomp(0, $redisData['trade']['BID_PRICE'], 6) != -1);
		} else {//空单平仓
			$check  = (bccomp(0, $redisData['trade']['ASK_PRICE'], 6) != -1);
		}
		if($check){
			return jsonReturn(-1,"无效操作[20001]",array(),TRUE); // 无效的操作
		}
		list($actionType,$ping) = explode('_', $action);

		//检查委托单量
		//代码待实现
		$positionTB = "cfd_cfd_position";
		$where = array('id'=>$posid,'flag'=>0,'uid'=>$uid);
		$query = $this->db->select()->from($positionTB)->where($where)->limit(1)->get();
		if ($query->row()){
			$chiData = $query->row_array();
		}else{
			return jsonReturn(-1,"无效操作[20002]",array(),TRUE); // 无效的操作
		}
		if($num=='all'){
			$num = $chiData['num'];
		}else{
			if($chiData['num'] < $num){
				return jsonReturn(-1,"平仓数量错误",array(),TRUE); // 平仓数量错误
			}
		}
		if($chiData['name']!=$this->name){
			return jsonReturn(-1,"无效操作[20003]",array(),TRUE); // 无效的操作
		}
		if($chiData['dtype']!=$this->dtype){
			return jsonReturn(-1,"无效操作[20004]",array(),TRUE); // 无效的操作
		}
		// 市价 Or 限价
		$type = empty($price) ? 0 : 1 ;
		$price = abs($price);
		$num = abs(intval($num));
		$datetime = date('Y-m-d H:i:s');
		$redis = $this->getRedis();
		try {
			$this->db->trans_start();//开始事务
			$redis->multi();

			// 生成订单
			$data_arr = array(
				'uid'            => $uid,
				'name'			 => $this->name,
				'dtype'			 => $this->dtype,
				'posid'			 => $posid,
				'type'           => $type,
				'times'			 => $chiData['times'],
				'kp'             => 'close',
				'price'          => $price,
				'num'            => $num,
				'commission'     => 0,
				'cjnum'          => 0,
				'flag'           => 0, // 0=未完成, 1=已完成
				'status'         => 0, // 0=未处理, 1=处理中, 2=已处理
				'ip'             => get_ip(),
				'comefrom'       => isset($comefrom)?$comefrom:1,
				'create_time'    => $datetime,
				'lastest_modify' => $datetime
			);
			if(!$this->db->insert('cfd_cfd_'.$actionType,$data_arr)) throw new Exception('下单失败[30002]', 1);
			$orderno = $this->db->insert_id();
			//放入队列处理
			if(isset($orderno) && $orderno>0){
				$queue1Key = $this->cfdConfig['common']['queue1']."_".$this->name;
				$try  = 2; // 推两次都不行就不推了……
				do {
					$len = $redis->lPush($queue1Key, $action.'|'.$this->dtype.'|'.$orderno);
					if ($len != false) break;
					--$try;
				} while($try > 0);
				if ($len == false) throw new Exception('L:'.__LINE__.' 队列推送失败!', 1);
			}
			// DB 提交修改
			$this->db->trans_complete();
			// Redis 提交修改
			$redis->exec();
		} catch (Exception $e) {
			$this->db->trans_rollback();
			$redis->discard();
			return jsonReturn(-1,$e->getMessage(),array(),TRUE);
		}
		return jsonReturn(0,"success",array('orderno'=>$orderno),TRUE);
	}

	/**
	 * 清用户缓存
	 * @return [type] [description]
	 */
	public function flushUserCache($uid){
		$CI =& get_instance();
        $default_config = array(
            'host' => '127.0.0.1',
            'port' => 6379,
            'timeout' => 0
        );
        if ($CI->config->load('redis', TRUE, TRUE)){
            $config = array_merge($default_config, $CI->config->item('redis'));
        }else{
            $config = $default_config;
        }
        $redis = new Redis();
        $redis->connect($config['host'],$config['port'],$config['timeout']);
        $redis->select(3);
        $rs = $redis->delete("USERINFO|$uid");
	}

	/**
	 * 计算保证金
	 * @param  [type] $num [description]
	 * @return [type]      [description]
	 */
	public function countBZJ($num, $times, $additional = 0, $action='close') {
		$rs = false;
		if($this->dtype=='ticket'){
			$rs = bcadd($num,0,0);
		}else{
			if ($additional != 0) $additional = $this->convertIndex($additional);
			$rs = bcmul($num, bcadd($times, $additional, 8),2);
			if($action=='open'){
				$extra_com = $this->denomConfig['extra_com'];
				$extra_value = bcmul($num, $extra_com, 2);
				$rs = bcadd($rs, $extra_value, 2);
			}
		}
		return $rs;
	}

	/**
	 * 计算爆仓价
	 * @param  float $avg_price  持仓加权均价
	 * @param  float $com        加权佣金指数
	 * @param  float $overnight  加权过夜费指数
	 * @param  boolean $bs       买卖方向
	 * @param  float $additional 追加保证金
	 * @return float
	 */
	public function countBaoPrice($avg_price, $com, $overnight, $bs, $times, $additional = 0) {
		$index = $this->denomConfig['index'];//每点跳值
		$dot = $this->productData['dot'];//报价保留多少位数
		$p_dot = $this->productData['p_dot'];//持仓均价保留多少位数
		$baoRate = $this->denomConfig['baoRate'];
		$tiao = bcdiv(1,pow(10,$dot),$dot);//最小跳幅
		$baoDot = bcmul(bcdiv($times,$index),$baoRate);//默认第一次的为80%爆仓
		$baoDot = bcadd($baoDot, $additional, 8);//加上追加点数
		$baoDot = bcsub(bcsub($baoDot, $com, 8) , $overnight, 8);//减去佣金、隔夜和隔周
		$stopLose = bcmul($baoDot, $tiao, 5);//转换成价格
		return $bs=='bid' ? bcsub($avg_price, $stopLose, $p_dot) : bcadd($avg_price, $stopLose ,$p_dot);
	}

	/**
	 * 计算加权均价
	 * @param  array $dataArr array('old_num', 'old_price', 'new_num', 'new_price', 'bs')
	 * @return float
	 */
	public function countAvgPrice($dataArr) {
		foreach ($dataArr as $key => $val) {
			$$key = $val;
		}
		unset($dataArr);
		$p_dot = $this->productData['p_dot'];//持仓均价保留多少位数
		// [(旧-num * 旧-price) + (新-num * 新-price)]/(旧-num + 新-num) -- 保留三位
		$avg_price = bcdiv(bcadd(bcmul($old_num, $old_price, 8), bcmul($new_num, $new_price, 8),8), bcadd($old_num, $new_num), 3);
		// 判断 bid / ask
		$pow = pow(10,$p_dot);
		$avg_price = bcmul($avg_price, pow(10,$p_dot), 4);
		if ($bs=='bid') {
			$avg_price = bcdiv(ceil($avg_price), pow(10,$p_dot), $p_dot);
		} else {
			$avg_price = bcdiv(floor($avg_price), pow(10,$p_dot), $p_dot);
		}
		return $avg_price;
	}

	/**
	 * 计算加权佣金
	 * @param  array $data_arr array('old_num', 'old_com', 'new_num', 'new_com')
	 * @return float
	 */
	public function countAvgCommission($data_arr) {
		foreach ($data_arr as $key => $val) {
			$$key = $val;
		}
		unset($data_arr);
		// [(旧-num * 旧-com) + (新-num * 新-com)]/(旧-num + 新-num) -- 保留八位
		return bcdiv(bcadd(bcmul($old_num, $old_com, 8), bcmul($new_num, $new_com, 8), 8) , bcadd($old_num, $new_num), 8);
	}

	/**
	 * 计算加权隔夜费
	 * @param  array $data_arr array('old_num', 'old_fee', 'new_num')
	 * @return float
	 */
	public function countAvgOvernightFee($data_arr) {
		foreach ($data_arr as $key => $val) {
			$$key = $val;
		}
		unset($data_arr);
		isset($old_fee) ? '' : $old_fee = 0;
		// (旧-num * 旧-fee)+ (新-num * 新-fee)/(旧-num + 新-num) -- 保留八位
		return bcdiv(bcadd(bcmul($old_num, $old_fee, 8), bcmul($new_num, $new_fee, 8), 8), bcadd($old_num, $new_num), 8);
	}

	/**
	 * 计算盈亏额
	 * @param  float $lastest_price 成交价
	 * @param  float $avg_price     加权均价
	 * @param  int $num           	量 - 迷你手
	 * @param  string $bs           多 | 空
	 * @param  float $com 			佣金 指数
	 * @param  float $overnight 	过夜费 指数
	 * @return int
	 */
	public function countYK($lastest_price,$avg_price, $num, $bs, $com, $overnight) {
		$currency = $this->denomConfig['currency'];
		$index = $this->denomConfig['index'];
		$ykdot = $this->denomConfig['ykdot'];
		$dot = $this->productData['dot'];//报价保留多少位数
		// echo $lastest_price.", ".$avg_price.", ".$num.", ".$bs.", ".$com.", ".$overnight.", ".$index.", ".$ykdot.", ".$dot."\n";
		$tiao = bcdiv(1,pow(10,$dot),$dot);//最小跳幅

		$gap   = bcsub($lastest_price, $avg_price, 5); // 价格差
		$tiao_gap = bcdiv($gap, $tiao, 5);
		if ($bs == 'ask') $tiao_gap = bcmul($tiao_gap, '-1', 5);
		// 扣除追加保证金、佣金 、过夜费
		$gap = bcsub(bcsub($tiao_gap, $com, 8), $overnight, 8);//跳差
		$yk = bcmul(bcmul($gap, $index, 8), $num, $ykdot);
		return $yk;
	}

	/**
	 * 计算盈亏率
	 * @param  float $yk         [description]
	 * @param  int $num        [description]
	 * @param  float $additional [description]
	 * @return float
	 */
	public function countYKL($yk, $margin) {
		return bcmul(bcdiv($yk,$margin,4), '100', 2);
	}

	/**
	 * 佣金/过夜费 指数 转为总佣金/过夜费
	 * @param  [type] $val [description]
	 * @param  [type] $num [description]
	 * @return float
	 */
	public function countTotalFee($val, $num) {
		return bcdiv(ceil(bcmul(bcmul($this->convertIndex($val), $num, 5), '100',1)),'100' ,2);
	}

	/**
	 * 指数 BTC 互换
	 * @param  number  $val      [description]
	 * @param  boolean $opposite [description]
	 * @return number
	 */
	public function convertIndex($val, $opposite = false) {
		$index = $this->denomConfig['index'];
		$minimum_value = $this->denomConfig['minimum_value'];
		return $opposite ? bcdiv(bcdiv($val, $minimum_value, 8), $index) : bcmul(bcmul($val, $minimum_value, 8), $index, 8);
	}

	public function updateCommission(){
		//选择redis DB
		$redis = $this->getRedis();
		$redis->hmset("HASH_".$this->name."_TRADE",array('WCP_BID_COM'=>5,'WCP_ASK_COM'=>5));
		return true;
	}

	/**
	 * 计算时令
	 */
	public function checkDST($name){
		$timezoneArr = $this->ci->config->item('timezone');
		$timezoneStr = isset($timezoneArr[$name])?$timezoneArr[$name]:'Asia/Shanghai';
		// echo $timezoneStr;
		$timezone=date('e');  //获取当前使用的时区
		date_default_timezone_set($timezoneStr); //强制设置时区
		$dst=date('I');  //判断是否夏令时
		date_default_timezone_set($timezone);  //还原时区
	    if($dst==1){//初入夏令
			return 0;
	    }else{
	    	return 1;
	    }
	}

	/**
	 * 检查是否交易时段
	 * @return boolen
	 */
	public function checkTime(){
		return true;
	}

	public function redisAllData(){
		//选择redis DB
		$redis = $this->getRedis();
		$data['trade'] = $redis->hgetall("HASH_".$this->name."_TRADE");
		return $data;
	}


}
